/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "webrtc/voice_engine/voe_dtmf_impl.h"

#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/voice_engine/channel.h"
#include "webrtc/voice_engine/include/voe_errors.h"
#include "webrtc/voice_engine/output_mixer.h"
#include "webrtc/voice_engine/transmit_mixer.h"
#include "webrtc/voice_engine/voice_engine_impl.h"

namespace webrtc {

VoEDtmf* VoEDtmf::GetInterface(VoiceEngine* voiceEngine) {
#ifndef WEBRTC_VOICE_ENGINE_DTMF_API
  return NULL;
#else
  if (NULL == voiceEngine) {
    return NULL;
  }
  VoiceEngineImpl* s = static_cast<VoiceEngineImpl*>(voiceEngine);
  s->AddRef();
  return s;
#endif
}

#ifdef WEBRTC_VOICE_ENGINE_DTMF_API

VoEDtmfImpl::VoEDtmfImpl(voe::SharedData* shared)
    : _dtmfFeedback(true), _dtmfDirectFeedback(false), _shared(shared) {
  WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "VoEDtmfImpl::VoEDtmfImpl() - ctor");
}

VoEDtmfImpl::~VoEDtmfImpl() {
  WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "VoEDtmfImpl::~VoEDtmfImpl() - dtor");
}

int VoEDtmfImpl::SendTelephoneEvent(int channel,
                                    int eventCode,
                                    bool outOfBand,
                                    int lengthMs,
                                    int attenuationDb) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SendTelephoneEvent(channel=%d, eventCode=%d, outOfBand=%d,"
               "length=%d, attenuationDb=%d)",
               channel, eventCode, (int)outOfBand, lengthMs, attenuationDb);
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
  voe::Channel* channelPtr = ch.channel();
  if (channelPtr == NULL) {
    _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
                          "SendTelephoneEvent() failed to locate channel");
    return -1;
  }
  if (!channelPtr->Sending()) {
    _shared->SetLastError(VE_NOT_SENDING, kTraceError,
                          "SendTelephoneEvent() sending is not active");
    return -1;
  }

  // Sanity check
  const int maxEventCode = outOfBand ? static_cast<int>(kMaxTelephoneEventCode)
                                     : static_cast<int>(kMaxDtmfEventCode);
  const bool testFailed = ((eventCode < 0) || (eventCode > maxEventCode) ||
                           (lengthMs < kMinTelephoneEventDuration) ||
                           (lengthMs > kMaxTelephoneEventDuration) ||
                           (attenuationDb < kMinTelephoneEventAttenuation) ||
                           (attenuationDb > kMaxTelephoneEventAttenuation));
  if (testFailed) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
                          "SendTelephoneEvent() invalid parameter(s)");
    return -1;
  }

  const bool isDtmf = (eventCode >= 0) && (eventCode <= kMaxDtmfEventCode);
  const bool playDtmfToneDirect =
      isDtmf && (_dtmfFeedback && _dtmfDirectFeedback);

  if (playDtmfToneDirect) {
    // Mute the microphone signal while playing back the tone directly.
    // This is to reduce the risk of introducing echo from the added output.
    _shared->transmit_mixer()->UpdateMuteMicrophoneTime(lengthMs);

    // Play out local feedback tone directly (same approach for both inband
    // and outband).
    // Reduce the length of the the tone with 80ms to reduce risk of echo.
    // For non-direct feedback, outband and inband cases are handled
    // differently.
    _shared->output_mixer()->PlayDtmfTone(eventCode, lengthMs - 80,
                                          attenuationDb);
  }

  if (outOfBand) {
    // The RTP/RTCP module will always deliver OnPlayTelephoneEvent when
    // an event is transmitted. It is up to the VoE to utilize it or not.
    // This flag ensures that feedback/playout is enabled; however, the
    // channel object must still parse out the Dtmf events (0-15) from
    // all possible events (0-255).
    const bool playDTFMEvent = (_dtmfFeedback && !_dtmfDirectFeedback);

    return channelPtr->SendTelephoneEventOutband(eventCode, lengthMs,
                                                 attenuationDb, playDTFMEvent);
  } else {
    // For Dtmf tones, we want to ensure that inband tones are played out
    // in sync with the transmitted audio. This flag is utilized by the
    // channel object to determine if the queued Dtmf e vent shall also
    // be fed to the output mixer in the same step as input audio is
    // replaced by inband Dtmf tones.
    const bool playDTFMEvent =
        (isDtmf && _dtmfFeedback && !_dtmfDirectFeedback);

    return channelPtr->SendTelephoneEventInband(eventCode, lengthMs,
                                                attenuationDb, playDTFMEvent);
  }
}

int VoEDtmfImpl::SetSendTelephoneEventPayloadType(int channel,
                                                  unsigned char type) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetSendTelephoneEventPayloadType(channel=%d, type=%u)", channel,
               type);
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
  voe::Channel* channelPtr = ch.channel();
  if (channelPtr == NULL) {
    _shared->SetLastError(
        VE_CHANNEL_NOT_VALID, kTraceError,
        "SetSendTelephoneEventPayloadType() failed to locate channel");
    return -1;
  }
  return channelPtr->SetSendTelephoneEventPayloadType(type);
}

int VoEDtmfImpl::GetSendTelephoneEventPayloadType(int channel,
                                                  unsigned char& type) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "GetSendTelephoneEventPayloadType(channel=%d)", channel);
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
  voe::Channel* channelPtr = ch.channel();
  if (channelPtr == NULL) {
    _shared->SetLastError(
        VE_CHANNEL_NOT_VALID, kTraceError,
        "GetSendTelephoneEventPayloadType() failed to locate channel");
    return -1;
  }
  return channelPtr->GetSendTelephoneEventPayloadType(type);
}

int VoEDtmfImpl::PlayDtmfTone(int eventCode, int lengthMs, int attenuationDb) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "PlayDtmfTone(eventCode=%d, lengthMs=%d, attenuationDb=%d)",
               eventCode, lengthMs, attenuationDb);

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (!_shared->audio_device()->Playing()) {
    _shared->SetLastError(VE_NOT_PLAYING, kTraceError,
                          "PlayDtmfTone() no channel is playing out");
    return -1;
  }
  if ((eventCode < kMinDtmfEventCode) || (eventCode > kMaxDtmfEventCode) ||
      (lengthMs < kMinTelephoneEventDuration) ||
      (lengthMs > kMaxTelephoneEventDuration) ||
      (attenuationDb < kMinTelephoneEventAttenuation) ||
      (attenuationDb > kMaxTelephoneEventAttenuation)) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
                          "PlayDtmfTone() invalid tone parameter(s)");
    return -1;
  }
  return _shared->output_mixer()->PlayDtmfTone(eventCode, lengthMs,
                                               attenuationDb);
}

int VoEDtmfImpl::SetDtmfFeedbackStatus(bool enable, bool directFeedback) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetDtmfFeedbackStatus(enable=%d, directFeeback=%d)",
               (int)enable, (int)directFeedback);

  CriticalSectionScoped sc(_shared->crit_sec());

  _dtmfFeedback = enable;
  _dtmfDirectFeedback = directFeedback;

  return 0;
}

int VoEDtmfImpl::GetDtmfFeedbackStatus(bool& enabled, bool& directFeedback) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "GetDtmfFeedbackStatus()");

  CriticalSectionScoped sc(_shared->crit_sec());

  enabled = _dtmfFeedback;
  directFeedback = _dtmfDirectFeedback;

  WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "GetDtmfFeedbackStatus() => enabled=%d, directFeedback=%d",
               enabled, directFeedback);
  return 0;
}
#endif  // #ifdef WEBRTC_VOICE_ENGINE_DTMF_API

}  // namespace webrtc
